﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Borg.Framework
{
    [Slot("Commands", OnPlugged = "CommandsPlugged", OnUnplugged = "CommandsUnplugged", Multiple = true)]
    [Plug("Borg.CommandInterpreter")]
    [Extension]
    public class CommandInterpreter : ICommandInterpreter
    {
        private readonly BackgroundWorker _argumentBackgroundWorker = new BackgroundWorker();
        private readonly BackgroundWorker _executeBackgroundWorker = new BackgroundWorker();

        internal readonly Dictionary<string, ICommand> commands = new Dictionary<string, ICommand>();
        
        private CommandState _state;

        public event EventHandler StateChanged;

        public CommandInterpreter()
        {
            commands.Add("help", new HelpCommand(commands));
            commands.Add("execute", new ExecuteCommand(this));
            
            _executeBackgroundWorker.DoWork += ExecuteCommand;
            _executeBackgroundWorker.RunWorkerCompleted += ExecuteCommandCompleted;

            _argumentBackgroundWorker.DoWork += GetArgument;
            _argumentBackgroundWorker.RunWorkerCompleted += GetArgumentCompleted;
        }

        public void CommandsPlugged(object sender, PlugEventArgs args)
        {
            var ext = (IConsoleCommands)args.Extension;

            foreach (var cmd in ext.Commands)
            {
                if (!commands.ContainsKey(cmd.Name))
                    commands.Add(cmd.Name, cmd);
                else
                    args.SlotInfo.ExtensionInfo.LogEvent(
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            "Error on adding command: \"{0}\". Command already exists.",
                            new object[] {cmd.Name}));
            }
        }

        public void CommandsUnplugged(object sender, PlugEventArgs args)
        {
            var ext = (IConsoleCommands)args.Extension;

            foreach (var cmd in ext.Commands.Where(cmd => commands.ContainsValue(cmd)))
                commands.Remove(cmd.Name);
        }

        public string Execute(string cmd)
        {
            string[] arg;
            var command = GetCommand(cmd, out arg);

            return
                command == null
                    ? (arg.Length > 0
                           ? string.Format("Command '{0}' not found.", arg[0])
                           : string.Empty)
                    : command.Handler(arg);
        }

        public bool ExecuteAsync(string cmd)
        {
            string[] arg;

            if (_state == CommandState.Running)
                return false;

            State = CommandState.Running;
            var command = GetCommand(cmd, out arg);
            
            if (command == null)
            {
                Output = arg.Length > 0
                    ? string.Format("Command '{0}' not found.", arg[0])
                    : string.Empty;

                State = CommandState.Completed;
                
                return true;
            }

            _executeBackgroundWorker.RunWorkerAsync(new ExecuteCommandInput(command, arg));
            
            return true;
        }

        private static void ExecuteCommand(object sender, DoWorkEventArgs e)
        {
            var arg = (ExecuteCommandInput)e.Argument;
            e.Result = arg.Command.Handler(arg.CommandArray);
        }

        private void ExecuteCommandCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Output = (string)e.Result;
            State = CommandState.Completed;
        }

        private static void GetArgument(object sender, DoWorkEventArgs e)
        {
            throw new NotImplementedException();
        }

        private static void GetArgumentCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            throw new NotImplementedException();
        }

        private ICommand GetCommand(string cmd, out string[] cmdArr)
        {
            cmdArr =
                cmd.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries);
            
            return commands.ContainsKey(cmdArr[0])
                ? commands[cmdArr[0]]
                : null;
        }

        public string[] GetCommandOptions(ref string cmdStart)
        {
            int length;
            var cmd = cmdStart.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            if (cmd.Length == 0)
                length = 0;
            else if (cmdStart[cmdStart.Length - 1] != ' ')
            {
                length = cmd.Length - 1;
                cmdStart = cmdStart.Substring(0, cmdStart.Length - cmd[length].Length);
            }
            else
                length = cmd.Length;

            IEnumerable<string> collection = null;

            if (length == 0)
                collection = commands.Keys;
            else
            {
                var key = cmd[0];

                if (commands.ContainsKey(key))
                    collection = commands[key].GetArguments(cmd, length - 1);
            }

            if (collection == null)
                return new string[0];

            var list = new List<string>();

            if (cmd.Length == length)
                list.AddRange(collection);
            else
            {
                var command = cmd[length];
                var len = cmd[length].Length;

                list.AddRange(collection.Where(item => (item.Length >= len) &&
                    item.Substring(0, len).Equals(command, StringComparison.CurrentCultureIgnoreCase)));
            }

            return list.ToArray();
        }

        public string Output { get; private set; }

        public CommandState State
        {
            get { return _state; }
            private set
            {
                _state = value;

                if (StateChanged != null)
                    StateChanged(this, EventArgs.Empty);
            }
        }
    }
}

